package com.yangfan.spider;

import java.text.DecimalFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.StringUtils;
import org.jsoup.Connection.Method;
import org.jsoup.Connection.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.yangfan.util.OCRutil;
import com.yangfan.util.SpiderJsoupUtil;

/**
 *
 * 发票实时爬虫<br>
 * <p>
 * https://inv-veri.chinatax.gov.cn/
 * <li>可查验最近1年内开具的发票。</li>
 * <li>当日开具发票可于次日进行查验。</li>
 * <li>每份发票每天最多可查验5次。</li>
 * <li>支持全部省份增值税发票查验。</li>
 * </p>
 * 
 * @author aixiang.zhu
 * @date 2016年12月9日 上午10:57:13
 */
/**
 * 发票实时爬虫
 * <p>
 * https://inv-veri.chinatax.gov.cn/
 * <li>可查验最近1年内开具的发票。</li>
 * <li>当日开具发票可于次日进行查验。</li>
 * <li>每份发票每天最多可查验5次。</li>
 * <li>支持全部省份增值税发票查验。</li>
 * </p>
 * 
 * @author liufei
 * com.yangfan.spider.InvoiceRealTimeSpider.java
 * 2017年3月9日 下午2:30:07
 */
public class InvoiceRealTimeSpider {

	private static Map<String, String> provinceUrl = new HashMap<String, String>();
	private static String captchaSuffix = "/yzmQuery?callback=jQuery110207055003200128072_";
	private static String querySuffix = "/query?callback=jQuery110207055003200128072_";
	private static Random random = new Random();
	private static Logger logger = LoggerFactory.getLogger(InvoiceRealTimeSpider.class);

	public static void main(String[] args) throws Exception {
		InvoiceRealTimeSpider is = new InvoiceRealTimeSpider();
		logger.info("start spider invoice==============={}", System.currentTimeMillis());
		String fpdm = "1200162320";
		String fphm = "20255295";
		String kprq = "20161101";
		String fpje = "669446";
		String info = is.getInfo(fpdm, fphm, kprq, fpje);
		logger.info("result is :{}", info);
	}

	/**
	 * 初始化各个省份的url！
	 */
	public void initUrl() {
		provinceUrl.put("1100", "https://zjfpcyweb.bjsat.gov.cn");// 北京
		provinceUrl.put("1200", "https://fpcy.tjsat.gov.cn");// 天津
		provinceUrl.put("1300", "https://fpcy.he-n-tax.gov.cn");// 河北
		provinceUrl.put("1400", "https://fpcy.tax.sx.cn");// 山西
		provinceUrl.put("1500", "https://fpcy.nm-n-tax.gov.cn");// 内蒙古
		provinceUrl.put("2100", "https://fpcy.tax.ln.cn");// 辽宁
		provinceUrl.put("2102", "https://fpcy.dlntax.gov.cn");// 大连
		provinceUrl.put("2200", "https://fpcy.jl-n-tax.gov.cn:4432");// 吉林
		provinceUrl.put("2300", "https://fpcy.hl-n-tax.gov.cn");// 黑龙江
		provinceUrl.put("3100", "https://fpcyweb.tax.sh.gov.cn:1001");// 上海
		provinceUrl.put("3200", "https://fpdk.jsgs.gov.cn:80");// 江苏
		provinceUrl.put("3300", "https://fpcyweb.zjtax.gov.cn");// 浙江
		provinceUrl.put("3302", "https://fpcy.nb-n-tax.gov.cn");// 宁波
		provinceUrl.put("3400", "https://fpcy.ah-n-tax.gov.cn");// 安徽
		provinceUrl.put("3500", "https://fpcyweb.fj-n-tax.gov.cn");// 福建
		provinceUrl.put("3502", "https://fpcy.xm-n-tax.gov.cn");// 厦门
		provinceUrl.put("3600", "https://fpcy.jxgs.gov.cn:82");// 江西
		provinceUrl.put("3700", "https://fpcy.sd-n-tax.gov.cn");// 山东
		provinceUrl.put("3702", "https://fpcy.qd-n-tax.gov.cn");// 青岛
		provinceUrl.put("4100", "https://fpcy.ha-n-tax.gov.cn");// 河南
		provinceUrl.put("4200", "https://fpcy.hb-n-tax.gov.cn");// 湖北
		provinceUrl.put("4300", "https://fpcy.hntax.gov.cn:8083");// 湖南
		provinceUrl.put("4400", "https://fpcy.gd-n-tax.gov.cn");// 广东
		provinceUrl.put("4403", "https://fpcy.szgs.gov.cn");// 深圳
		provinceUrl.put("4500", "https://fpcy.gxgs.gov.cn:8200");// 广西
		provinceUrl.put("4600", "https://fpcy.hitax.gov.cn");// 海南
		provinceUrl.put("5000", "https://fpcy.cqsw.gov.cn:80");// 重庆
		provinceUrl.put("5100", "https://fpcy.sc-n-tax.gov.cn");// 四川
		provinceUrl.put("5200", "https://fpcy.gz-n-tax.gov.cn:80");// 贵州
		provinceUrl.put("5300", "https://fpcy.yngs.gov.cn");// 云南
		provinceUrl.put("5400", "https://fpcy.xztax.gov.cn:81");// 西藏
		provinceUrl.put("6100", "https://fpcyweb.sn-n-tax.gov.cn");// 陕西
		provinceUrl.put("6200", "https://fpcy.gs-n-tax.gov.cn");// 甘肃
		provinceUrl.put("6300", "https://fpcy.qh-n-tax.gov.cn");// 青海
		provinceUrl.put("6400", "https://fpcy.nxgs.gov.cn");// 宁夏
		provinceUrl.put("6500", "https://fpcy.xj-n-tax.gov.cn");// 新疆
	}

	/**
	 * 
	 * @param fpdm
	 *            发票代码
	 * @param fphm
	 *            发票号码
	 * @param kprq
	 *            开票日期
	 * @param fpje
	 *            发票校验码（后六位） 或 开具金额（增值税专用发票）
	 * @return
	 */
	public String getInfo(String fpdm, String fphm, String kprq, String fpje) {
		initUrl();
		JSONObject obj = new JSONObject();
		if (StringUtils.isNotBlank(fpdm) && StringUtils.isNotBlank(fphm) && StringUtils.isNotBlank(kprq)
				&& StringUtils.isNotBlank(fpje)) {
			try {
				String result = accessUrl(fpdm, fphm, kprq, fpje);
				if (StringUtils.isBlank(result)) {
					obj.put("status", "-1");
					obj.put("msg", "没有获取到数据！");
				} else {
					JSONObject data = parse(result, fpdm, fphm, kprq);
					obj.put("status", "0");
					obj.put("data", data);
				}
			} catch (Exception e) {
				obj.put("status", "-1");
				obj.put("msg", "解析异常！");
				logger.error("解析异常--", e);
			}
		} else {
			obj.put("status", "-1");
			obj.put("msg", "参数不全！");
		}
		return obj.toJSONString();
	}

	/**
	 * key1：<br>
	 * 001：正常<br>
	 * 002：超过该张票当天查验次数(第二天再查)！<br>
	 * 003：发票查验请求太频繁，请稍后再试！<br>
	 * 004：超过服务器最大请求数，请稍后访问！<br>
	 * 005：请求不合法！<br>
	 * 006：不一致！<br>
	 * 007：验证码失效！<br>
	 * 008：验证码错误！<br>
	 * 009：查无此票！<br>
	 * rgerr：当日开具发票可于次日进行查验！<br>
	 * 020：由于查验行为异常，涉嫌违规，当前无法使用查验服务！<br>
	 * 以外：查验系统发生异常!(04)<br>
	 * 
	 * @param params
	 * @return
	 */
	private String accessUrl(String fpdm, String fphm, String kprq, String fpje) {
		// 验证码严重不通过，重试，20次
		for (int i = 0; i < 20; i++) {
			try {
				JSONObject captchaObj = getVcode(fpdm);
				if (captchaObj == null) {
					return "";
				}
				Map<String, String> map = new HashMap<String, String>();
				map.put("fpdm", fpdm);// 发票代码
				map.put("fphm", fphm);// 发票号码
				map.put("kprq", kprq);// 开票日期
				map.put("fpje", fpje);// 发票金额
				map.put("fplx", getFplx(fpdm));// 发票类型
				map.put("yzm", captchaObj.getString("vcode"));// 验证码
				map.put("yzmSj", captchaObj.getString("key2"));// 验证码失效时间
				map.put("index", captchaObj.getString("key3"));// 验证码参数，应该是与后台对应验证码匹配
				map.put("iv", "2755a9254e85f3793ca5c7aa842540e2");// aes加密参数
				map.put("salt", "3593a23eaa75341cf4280ef91557dcb3");// aes加密参数
				long timestamp = new Date().getTime();
				String url = getUrl(fpdm) + querySuffix + timestamp + "&_=" + timestamp + "&"
						+ SpiderJsoupUtil.getUrlParamsByMap(map);
				logger.info("request url is {}", url);
				Response response = SpiderJsoupUtil.getContent(url, null, null, Method.GET, getHeader());
				if (response.statusCode() == 200) {
					String result = response.body();
					logger.info(result);
					result = result.substring(result.indexOf("(") + 1, result.lastIndexOf(")"));
					JSONObject obj = JSONObject.parseObject(result);
					if ("008".equals(obj.getString("key1")) || "007".equals(obj.getString("key1"))) {
						continue;
					} else {
						return obj.toJSONString();
					}
				} else {
					logger.error("请求出错！--" + response.statusCode() + "--" + response.statusMessage());
					break;
				}
			} catch (Exception e) {
				logger.error("发生异常！--",e);
				break;
			}
		}
		return "";
	}

	/**
	 * 
	 * @param content
	 *            返回的数据内容
	 * @param fpdm
	 *            发票代码
	 * @param fphm
	 *            发票号码
	 * @param kprq
	 *            开票日期
	 * @return
	 */
	private JSONObject parse(String content, String fpdm, String fphm, String kprq) throws Exception {
		JSONObject obj = JSONObject.parseObject(content);
		JSONObject result = new JSONObject();
		if ("582ae".equals(obj.getString("key11"))) {
			String split = "□#▽";
			result = parseDetail(content, fpdm, fphm, kprq, obj, split);
		}
		if ("dc1de".equals(obj.getString("key11"))) {
			String split = "▽#□";
			result = parseDetail(content, fpdm, fphm, kprq, obj, split);
		}
		return result;
	}

	/**
	 * 根据不同的split解析不同的格式
	 * 
	 * @param content
	 * @param fpdm
	 * @param fphm
	 * @param kprq
	 * @param obj
	 * @param split
	 * @return
	 */
	private JSONObject parseDetail(String content, String fpdm, String fphm, String kprq, JSONObject obj,
			String split) {
		String rule1 = split.split("#")[0];
		String rule2 = split.split("#")[1];
		JSONObject result = new JSONObject();
		if (obj.getString("key1").equals("001")) {
			String fplx = getFplx(fpdm);
			DecimalFormat df = new DecimalFormat("#.00");
			if ("10".equals(fplx)) {
				String[] fpxxs = obj.getString("key2").split(rule1);
				result.put("发票类型", "增值税电子普通发票");
				result.put("销售方地址及电话", fpxxs[1]);
				result.put("查验次数", Integer.valueOf(fpxxs[2]) + 1);
				result.put("销售方开户行及账号", fpxxs[5]);
				result.put("校验码", fpxxs[8]);
				result.put("购买方名称", fpxxs[14]);
				result.put("销售方名称", fpxxs[15]);
				result.put("机器编码", fpxxs[13]);
				result.put("发票代码", fpdm);
				result.put("发票号码", fphm);
				result.put("开票日期", kprq);
				String[] hwxxs = obj.getString("key3").split(rule1);
				JSONArray array = new JSONArray();
				double sum = 0.0;
				double tax = 0.0;
				double total = 0.0;
				for (int i = 0; i < hwxxs.length; i++) {
					String[] hwxxss = hwxxs[i].replaceAll(rule2, "").split("█");
					JSONObject item = new JSONObject();
					item.put("名称", hwxxss[0]);
					item.put("规定型号", StringUtils.isBlank(hwxxss[1]) ? "" : hwxxss[1]);
					item.put("单位", StringUtils.isBlank(hwxxss[2]) ? "" : hwxxss[2]);
					item.put("数量", StringUtils.isBlank(hwxxss[6]) ? "" : hwxxss[6]);
					item.put("单价", StringUtils.isBlank(hwxxss[4]) ? "" : Double.valueOf(hwxxss[4]));
					item.put("金额", Double.valueOf(hwxxss[5]));
					sum += Double.valueOf(hwxxss[5]);
					item.put("税率", hwxxss[3] + "%");
					item.put("税额", Double.valueOf(hwxxss[7]));
					tax += Double.valueOf(hwxxss[7]);
					array.add(item);
				}
				total = sum + tax;
				result.put("交税内容", array);
				result.put("金额合计", Double.valueOf(df.format(sum)));
				result.put("税额合计", Double.valueOf(df.format(tax)));
				result.put("价税合计", Double.valueOf(df.format(total)));
			} else if ("04".equals(fplx)) {
				String[] fpxxs = obj.getString("key2").split(rule1);
				result.put("发票类型", "增值税普通发票");
				result.put("销售方地址及电话", fpxxs[1]);
				result.put("查验次数", Integer.valueOf(fpxxs[2]) + 1);
				result.put("销售方开户行及账号", fpxxs[5]);
				result.put("校验码", fpxxs[8]);
				result.put("购买方名称", fpxxs[14]);
				result.put("销售方名称", fpxxs[15]);
				result.put("机器编码", fpxxs[17]);
				result.put("发票代码", fpdm);
				result.put("发票号码", fphm);
				result.put("开票日期", kprq);
				String[] hwxxs = obj.getString("key3").split(rule1);
				JSONArray array = new JSONArray();
				double sum = 0.0;
				double tax = 0.0;
				double total = 0.0;
				for (int i = 0; i < hwxxs.length; i++) {
					String[] hwxxss = hwxxs[i].replaceAll(rule2, "").split("█");
					JSONObject item = new JSONObject();
					item.put("名称", StringUtils.isBlank(hwxxss[0]) ? "" : hwxxss[0]);
					item.put("规定型号", StringUtils.isBlank(hwxxss[1]) ? "" : hwxxss[1]);
					item.put("单位", StringUtils.isBlank(hwxxss[2]) ? "" : hwxxss[2]);
					item.put("数量", StringUtils.isBlank(hwxxss[3]) ? "" : Integer.valueOf(hwxxss[3]));
					item.put("单价", StringUtils.isBlank(hwxxss[4]) ? "" : Double.valueOf(hwxxss[4]));
					item.put("金额", Double.valueOf(hwxxss[5]));
					sum += Double.valueOf(hwxxss[5]);
					item.put("税率", hwxxss[6] + "%");
					item.put("税额", Double.valueOf(hwxxss[7]));
					tax += Double.valueOf(hwxxss[7]);
					array.add(item);
				}
				total = sum + tax;
				result.put("交税内容", array);
				result.put("金额合计", Double.valueOf(df.format(sum)));
				result.put("税额合计", Double.valueOf(df.format(tax)));
				result.put("价税合计", Double.valueOf(df.format(total)));
			} else if ("01".equals(fplx)) {
				String[] fpxxs = obj.getString("key2").split(rule1);
				result.put("发票类型", "增值税专用发票");
				result.put("购买方地址及电话", fpxxs[1]);
				result.put("查验次数", Integer.valueOf(fpxxs[2]) + 1);
				// result.put("购买方纳税人识别号", fpxxs[4]);
				result.put("购买方开户行及账号", fpxxs[5]);
				// result.put("销售方纳税人识别号", fpxxs[6]);
				result.put("销售方地址及电话", fpxxs[7]);
				result.put("销售方开户行及账号", fpxxs[9]);
				result.put("销售方名称", fpxxs[14]);
				result.put("购买方名称", fpxxs[15]);
				result.put("机器编码", fpxxs[17]);
				result.put("发票代码", fpdm);
				result.put("发票号码", fphm);
				result.put("开票日期", kprq);
				String[] hwxxs = obj.getString("key3").split(rule1);
				JSONArray array = new JSONArray();
				double sum = 0.0;
				double tax = 0.0;
				double total = 0.0;
				for (int i = 0; i < hwxxs.length; i++) {
					String[] hwxxss = hwxxs[i].replaceAll(rule2, "").split("█");
					JSONObject item = new JSONObject();
					item.put("名称", StringUtils.isBlank(hwxxss[0]) ? "" : hwxxss[0]);
					item.put("规定型号", StringUtils.isBlank(hwxxss[1]) ? "" : hwxxss[1]);
					item.put("单位", StringUtils.isBlank(hwxxss[2]) ? "" : hwxxss[2]);
					item.put("数量", StringUtils.isBlank(hwxxss[3]) ? "" : Integer.valueOf(hwxxss[3]));
					item.put("单价", StringUtils.isBlank(hwxxss[4]) ? "" : Double.valueOf(hwxxss[4]));
					item.put("金额", Double.valueOf(hwxxss[5]));
					sum += Double.valueOf(hwxxss[5]);
					item.put("税率", hwxxss[6] + "%");
					item.put("税额", Double.valueOf(hwxxss[7]));
					tax += Double.valueOf(hwxxss[7]);
					array.add(item);
				}
				total = sum + tax;
				result.put("交税内容", array);
				result.put("金额合计", Double.valueOf(df.format(sum)));
				result.put("税额合计", Double.valueOf(df.format(tax)));
				result.put("价税合计", Double.valueOf(df.format(total)));
			} else {
				result.put("发票代码", fpdm);
				result.put("发票号码", fphm);
				result.put("开票日期", kprq);
				result.put("error", "暂不支持的发票类型，我们将尽快进行添加！");
				logger.info(content);
			}
		} else {
			if (obj.getString("key1").equals("002")) {
				result.put("errorId", "002");
				result.put("errorMsg", "超过该张票当天查验次数(第二天再查)！");
			} else if (obj.getString("key1").equals("003")) {
				result.put("errorId", "003");
				result.put("errorMsg", "发票查验请求太频繁，请稍后再试！");
			} else if (obj.getString("key1").equals("004")) {
				result.put("errorId", "004");
				result.put("errorMsg", "超过服务器最大请求数，请稍后访问！");
			} else if (obj.getString("key1").equals("005")) {
				result.put("errorId", "005");
				result.put("errorMsg", "请求不合法！");
			} else if (obj.getString("key1").equals("006")) {
				result.put("errorId", "006");
				result.put("errorMsg", "不一致！");
			}
			// else if (result.getString("key1").equals("007")) {
			// result.put("error", "验证码失效！");
			// } else if (result.getString("key1").equals("008")) {
			// result.put("error", "验证码错误！");
			// }
			else if (obj.getString("key1").equals("009")) {
				result.put("errorId", "009");
				result.put("errorMsg", "查无此票！");
			} else if (obj.getString("key1").equals("rgerr")) {
				result.put("errorId", "regerr");
				result.put("errorMsg", "当日开具发票可于次日进行查验！");
			} else if (obj.getString("key1").equals("020")) {
				result.put("errorId", "020");
				result.put("errorMsg", "由于查验行为异常，涉嫌违规，当前无法使用查验服务！");
			} else {
				result.put("errorId", "未知");
				result.put("errorMsg", "查验系统发生异常!");
			}
		}
		return result;
	}

	/**
	 * 请求头，必须
	 * 
	 * @return
	 */
	private Map<String, String> getHeader() {
		Map<String, String> header = new HashMap<String,String>();
		header.put("Referer", "https://inv-veri.chinatax.gov.cn/");
		return header;
	}

	/**
	 * 验证码请求获得的数据：<br>
	 * key1：验证码base64字符<br>
	 * key2：验证码失效时间<br>
	 * key3：验证码索引<br>
	 * key4：验证码类型--00：文字，01：红色文字，02：黄色文字，03：蓝色文字
	 * 
	 * @param fpdm
	 *            发票代码
	 * @return
	 */
	private JSONObject getVcode(String fpdm) {
		try {
			for (int i = 0; i < 10; i++) {
				long timestamp = new Date().getTime();
				String url = getUrl(fpdm) + captchaSuffix + timestamp + "&fpdm=" + fpdm + "&r=" + random.nextDouble()
						+ "&_=" + timestamp;
				Response response = SpiderJsoupUtil.getContent(url, null, null, Method.GET, getHeader());
				String captchaResult = response.body();
				captchaResult = captchaResult.substring(captchaResult.indexOf("(") + 1, captchaResult.indexOf(")"));
				JSONObject captchaObj = JSONObject.parseObject(captchaResult);
				String vcode = OCRutil.doocr(captchaResult.getBytes(), 49);
				if (vcode == null) {
					TimeUnit.SECONDS.sleep(1);
					continue;
				}
				captchaObj.put("vcode", vcode == null ? "" : vcode);
				return captchaObj;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 根据发票代码获取发票类型
	 * 
	 * @param fpdm
	 *            发票代码
	 * @return 01-增值税专用发票，04-增值税普通发票，02-货物运输业增值税专用发票，
	 *         03-机动车销售统一发票，10-增值税电子普通发票，11-增值税普通发票（卷票）
	 */
	public String getFplx(String fpdm) {
		String b;
		String b1, b2;
		String c = "99";
		String[] code = { "144031539110", "131001570151", "133011501118", "111001571071", "111001471071",
				"111001471072", "111001571072", "111001671071", "111001671072" };
		if (fpdm.length() == 12) {
			b1 = fpdm.substring(0, 1);
			b2 = fpdm.substring(10, 12);
			b = fpdm.substring(7, 8);
			if ("3100".equals(fpdm.substring(1, 5))) {
				if ("701".equals(fpdm.substring(7, 10))) {
					c = "10";
				}
			} else if ("33".equals(fpdm.substring(1, 3))) {
				if ("18".equals(b2)) {
					c = "10";
				}
			} else if ("4403".equals(fpdm.substring(1, 5))) {
				if (("15".equals(fpdm.substring(5, 7)) && "39".equals(fpdm.substring(7, 9)))
						|| ("16".equals(fpdm.substring(5, 7)) && "11".equals(fpdm.substring(10, 12)))) {
					c = "10";
				}
			}
			for (int i = 0; i < code.length; i++) {
				if (fpdm.equals(code[i])) {
					c = "10";
				}
			}
			if ("99".equals(c) && "0".equals(b1) && "11".equals(b2)) {
				c = "10";
			}
			if ("99".equals(c)) {
				if ("2".equals(b)) {
					c = "03";
				} else {
					c = "11";
				}
			}
		} else if (fpdm.length() == 10) {
			b = fpdm.substring(7, 8);
			if ("1".equals(b) || "5".equals(b)) {
				c = "01";
			} else if ("6".equals(b) || "3".equals(b)) {
				c = "04";
			} else if ("7".equals(b) || "2".equals(b)) {
				c = "02";
			}
		}
		return c;
	}

	/**
	 * 根据发票代码获取需要请求的url
	 * 
	 * @param fpdm
	 *            发票代码
	 * @return
	 * @throws Exception
	 */
	private String getUrl(String fpdm) throws Exception {
		String dqdm = "";
		if (fpdm.length() == 12) {
			dqdm = fpdm.substring(1, 5);
		} else {
			dqdm = fpdm.substring(0, 4);
		}
		if (!dqdm.equals("2102") && !dqdm.equals("3302") && !dqdm.equals("3502") && !dqdm.equals("3702")
				&& !dqdm.equals("4403")) {
			dqdm = dqdm.substring(0, 2) + "00";
		}
		if (StringUtils.isNotBlank(provinceUrl.get(dqdm))) {
			return provinceUrl.get(dqdm) + "/WebQuery";
		} else {
			throw new Exception("发票代码有误，找不到对应的url");
		}
	}

}
